#import "VideoPlayer.h"
#include "Keystoner.h"

#import <QTKit/QTKit.h>
#import "Network.h"

@implementation VideoPlayer

-(void) initPlugin{	[self addProperty:[BoolProperty boolPropertyWithDefaultvalue:0] named:@"userbutton"];	
	[self addProperty:[NumberProperty sliderPropertyWithDefaultvalue:0 minValue:0 maxValue:4] named:@"machine"];
	[self addProperty:[NumberProperty sliderPropertyWithDefaultvalue:0 minValue:0 maxValue:12] named:@"video"];	
	[self addProperty:[NumberProperty sliderPropertyWithDefaultvalue:0 minValue:0 maxValue:4] named:@"latency"];
	[self addProperty:[NumberProperty sliderPropertyWithDefaultvalue:0 minValue:0 maxValue:1] named:@"fade"];	
	[self addProperty:[NumberProperty sliderPropertyWithDefaultvalue:0 minValue:0 maxValue:1] named:@"fadeTo"];	
	[self addProperty:[NumberProperty sliderPropertyWithDefaultvalue:0 minValue:0 maxValue:12] named:@"fadeToVideo"];	
	
	lastFramesVideo = 0;
	whiteFlash = 0;
	forceDrawNextFrame = NO;
	latencyTimer = nil;
	
	timecodeSetterTimer = [[NSDate date] retain];
	
}

-(void) sendTimecode{
	ofxOscMessage m;			
	m.setAddress("/timecode/set");
	m.addFloatArg([movie[int(PropF(@"video"))] currentTime].timeValue);		
	m.addIntArg(int(PropF(@"video")));
	
	oscDirectSender[3]->sendMessage(m);	
	oscDirectSender[2]->sendMessage(m);	
	
	[timecodeSetterTimer release];
	timecodeSetterTimer = [[NSDate date] retain];
}

-(void) setRemoteVideos:(int) video{
	ofxOscMessage m;			
	m.setAddress("/pluginProperty/set");
	m.addStringArg("VideoPlayer");
	m.addStringArg("fadeToVideo");
	m.addIntArg(video);				
	oscSender[2]->sendMessage(m);	
	oscSender[3]->sendMessage(m);	
	
	ofxOscMessage m2;			
	m2.setAddress("/pluginProperty/set");
	m2.addStringArg("VideoPlayer");
	m2.addStringArg("fadeTo");
	m2.addIntArg(1);				
	oscSender[2]->sendMessage(m2);	
	oscSender[3]->sendMessage(m2);	
	
	
	/*
	 ofxOscMessage m;			
	 m.setAddress("/pluginProperty/set");
	 m.addStringArg("VideoPlayer");
	 m.addStringArg("video");
	 m.addFloatArg(video);		
	 
	 if(latencyTimer != nil)
	 [latencyTimer release];
	 latencyTimer = [[NSDate date] retain];
	 
	 oscSender[3]->sendMessage(m);	
	 oscSender[2]->sendMessage(m);	*/
}




-(IBAction) restart:(id)sender{
	[Prop(@"video") setIntValue:0];
	[movie[0] setCurrentTime:QTMakeTime(0, 60)];
}

-(void)dealloc {
    [super dealloc];
}

-(void) setLocalVideo:(NSNumber*)video{
	[Prop(@"video") setFloatValue:[video intValue]];
	
}

-(void) setFadeTo:(NSNumber*)val{
	[Prop(@"fadeTo") setValue:val];
}

-(BOOL) willDraw:(NSMutableDictionary *)drawingInformation{
	if(forceDrawNextFrame){
		forceDrawNextFrame = NO;
		return YES;
	}
	
	if([movie[int(PropF(@"video"))] rate] != 1)
		dispatch_async(dispatch_get_main_queue(), ^{				
			[movie[int(PropF(@"video"))] setRate:1]; 
		});
	
	const CVTimeStamp * outputTime;
	[[drawingInformation objectForKey:@"outputTime"] getValue:&outputTime];
	if(textureContext[int(PropF(@"video"))] != nil)
		return QTVisualContextIsNewImageAvailable(textureContext[int(PropF(@"video"))], outputTime);
	return NO;
	
}

-(void) setup{
	[Prop(@"video") setFloatValue:0];
	[Prop(@"fade") setFloatValue:0];
	[Prop(@"fadeTo") setFloatValue:0];
	[Prop(@"fadeToVideo") setFloatValue:0];
	
	
	
	[Prop(@"userbutton") setFloatValue:0];
	
	if(PropF(@"machine") <= 1){
		ofSetLogLevel(OF_LOG_NOTICE);
		arduino = new ofSerial();
		arduino->enumerateDevices();
		arduinoConnected = arduino->setup(0,115200);
		
		[arduinoConnectionImage setHidden:NO];
		[arduinoConnectionText setHidden:NO];
		
		if(arduinoConnected)
			[arduinoConnectionImage setImage:[NSImage imageNamed:@"tick"]];
	}
	
	
	
	for(int i=0;i<4;i++){
		oscSender[i] = new ofxOscSender();
		oscDirectSender[i] = new ofxOscSender();
		
		if(PropF(@"machine") != i){
			switch (i) {
				case 0:
					oscSender[i]->setup("halfdanj.local", 1111);
					oscDirectSender[i]->setup("halfdanj.local", 22222);
					break;
				case 1:
					oscSender[i]->setup("globus"+ofToString(i)+".local", 1111);					
					oscDirectSender[i]->setup("globus"+ofToString(i)+".local", 2222);					
					break;					
				case 2:
					oscSender[i]->setup("globus"+ofToString(i)+".local", 1111);					
					oscDirectSender[i]->setup("globus"+ofToString(i)+".local", 2222);					
					break;					
				case 3:
					oscSender[i]->setup("globus"+ofToString(i)+".local", 1111);					
					oscDirectSender[i]->setup("globus"+ofToString(i)+".local", 2222);					
					break;					
				default:
					break;
			}
		}
	}
	oscReceiver = new ofxOscReceiver();
	oscReceiver->setup(2222);
	
	if(PropF(@"machine") <= 1){
		[self setRemoteVideos:0];
	}	
	
	
	dispatch_async(dispatch_get_main_queue(), ^{				
		NSError * error[NUMVIDEOS];
		for(int i=0;i<NUMVIDEOS;i++){
			error[i] = [NSError alloc];	
		}
		
		keys[0][0] = @"0:0:0:00.00/600";
		keys[0][1] = @"0:0:0:02.00/600";
		
		keys[1][0] = @"0:0:0:17.96/600";
		keys[1][1] = @"0:0:0:21.84/600";
		
		NSArray * paths1 = [NSArray arrayWithObjects:
							@"~/Sync/Movies/070910/01-00-globe loop-060910 h264.mov", 
							@"~/Sync/Movies/070910/01-01-opdager-070910 h264.mov",
							@"~/Sync/Movies/070910/01-03-Da Vinci-060910 h264.mov",nil];
		
		NSArray * paths2 = [NSArray arrayWithObjects:
							@"~/Sync/Movies/070910/02-00-globe loop-floor-090910.mov", 
							@"~/Sync/Movies/070910/02-01-opdager-floor-060910 h264.mov",
							@"~/Sync/Movies/070910/02-03-da vinci-floor-060910.mov",nil];
		
		NSArray * paths3 = [NSArray arrayWithObjects:
							@"~/Sync/Movies/070910/03-00-globe loop-wall-090910.mov", 
							@"~/Sync/Movies/070910/03-01-opdager-wall-060910 h264.mov",
							@"~/Sync/Movies/070910/03-03-da vinci-wall-060910.mov",nil];
		
		
		NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
									  //	  [NSNumber numberWithBool:YES], QTMovieOpenForPlaybackAttribute,
									  [NSNumber numberWithBool:NO], QTMovieOpenAsyncOKAttribute,
									  [NSNumber numberWithBool:NO], QTMovieLoopsAttribute, nil];		
		
		int i=0;
		
		switch (int(PropF(@"machine"))) {
			case 0: //Test maskine
				
				[dict setObject:@"/Users/jonas/Documents/Projekter/Museer/Globerummet/sync/Movies/070910/03-00-globe loop-wall-090910.mov" forKey:QTMovieFileNameAttribute];
				movie[0] = [[QTMovie alloc] initWithAttributes:dict error:&error[0]];
				[dict setObject:@"/Users/jonas/Documents/Projekter/Museer/Globerummet/sync/Movies/070910/03-03-da vinci-wall-060910.mov" forKey:QTMovieFileNameAttribute];
				movie[1] = [[QTMovie alloc] initWithAttributes:dict error:&error[1]];
				[dict setObject:@"/Users/jonas/Documents/Projekter/Museer/Globerummet/sync/Movies/070910/03-03-da vinci-wall-060910.mov" forKey:QTMovieFileNameAttribute];
				movie[2] = [[QTMovie alloc] initWithAttributes:dict error:&error[2]];
				
				break;
				
			case 1:
				for(NSString * path in paths1){
					NSLog(@"Load %@",path);
					[dict setObject:[path stringByExpandingTildeInPath] forKey:QTMovieFileNameAttribute];
					movie[i] = [[QTMovie alloc] initWithAttributes:dict error:&error[i]];
					i++;
				}
				break;		
				
			case 2:
				for(NSString * path in paths2){
					[dict setObject:[path stringByExpandingTildeInPath] forKey:QTMovieFileNameAttribute];
					movie[i] = [[QTMovie alloc] initWithAttributes:dict error:&error[i]];
					i++;
				}
				break;
				
			case 3:
				for(NSString * path in paths3){
					[dict setObject:[path stringByExpandingTildeInPath] forKey:QTMovieFileNameAttribute];
					movie[i] = [[QTMovie alloc] initWithAttributes:dict error:&error[i]];
					i++;
				}
				break;
				
			default:
				break;
		}
		
		for(int i=0;i<NUMVIDEOS;i++){
			if(error[i] != nil) NSLog(@"ERROR: Could not load movie %i: %@",i,error[i]);
		}
		
		for(int i=0;i<NUMVIDEOS;i++){
			[movie[i] retain];
			[movie[i] stop];
			[movie[i] setAttribute:[NSNumber numberWithBool:NO] forKey:QTMovieLoopsAttribute];
			
			QTOpenGLTextureContextCreate(kCFAllocatorDefault,								
										 CGLContextObj([[[[[globalController viewManager] outputViews] objectAtIndex:0] openGLContext] CGLContextObj]),		// the OpenGL context
										 CGLGetPixelFormat(CGLGetCurrentContext()),
										 nil,
										 &textureContext[i]);
			SetMovieVisualContext([movie[i] quickTimeMovie], textureContext[i]);			
			//[movie[i] setVisualContext:textureContext[i]];
		}
		
		[movie[0] setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieLoopsAttribute];
		[movie[0] play];
		[movie[0] setRate:1.0];
	});	
	
	//	sphereShader = new ofxShader();
	//sphereShader->loadShader("sphere");
}

-(void) sleepComputer{
	NSDictionary* errorDict;
	NSAppleEventDescriptor* returnDescriptor = NULL;
	
	
	NSAppleScript*  scriptObject= [[NSAppleScript alloc] initWithSource:
								   [NSString stringWithFormat:
									@"tell application \"Finder\" to sleep"] 
								   ];					
	returnDescriptor = [scriptObject executeAndReturnError: &errorDict];	
}

-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
	NSLog(@"Observe: %@",context);
}

-(void) update:(NSDictionary *)drawingInformation{	
	
	int i= PropF(@"video");	
	
	
	if(PropF(@"machine") <= 1){
		//HOST
		if(arduinoConnected){
			while(arduino->available()){
				//Message from arduino
				char byte = arduino->readByte();
				if(byte == 'b'){
					//Button pressed
					[Prop(@"userbutton") setBoolValue:YES];
				}else if(byte == 'o'){
					//Turn off
					NSLog(@"	Turn off");
					
					NSDictionary* errorDict;
					NSAppleEventDescriptor* returnDescriptor = NULL;
					
					
					NSAppleScript*  scriptObject= [[NSAppleScript alloc] initWithSource:
												   [NSString stringWithFormat:
													@"tell application \"Finder\" of machine \"eppc://globus2:kronborg@globus2.local\"  to sleep \n tell application \"Finder\" of machine \"eppc://globus3:kronborg@globus3.local\"  to sleep \n tell application \"Finder\" to sleep"] 
												   ];					
					returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
					
					[self performSelector:@selector(sleepComputer) withObject:nil afterDelay:3];
					
				}
			}
		}
		
		if(PropF(@"userbutton")){// || ofRandom(0, 1) < 0.01){
			//Button is pressed
			[Prop(@"userbutton") setBoolValue:NO];
			if(PropF(@"video") == 0){
				//We are in video 0
				for(int u=0;u<NUMVIDEOS-1;u++){
					if([movie[0] currentTime].timeValue > QTTimeFromString(keys[u][0]).timeValue && [movie[0] currentTime].timeValue < QTTimeFromString(keys[u][1]).timeValue){	
						//We are in a period where we can press the button
						if(PropF(@"latency") > 0){
							dispatch_async(dispatch_get_main_queue(), ^{											
								[self performSelector:@selector(setLocalVideo:) withObject:[NSNumber numberWithInt:u+1] afterDelay:PropF(@"latency")];						
							});
						} else {
							[self setLocalVideo:[NSNumber numberWithInt:u+1]];
						}
						
						//Set the remote to that video aswell
						[self setRemoteVideos:u+1];
					}
				}
			}					
		}	
		
		//Host receives OSC message
		/*	while(oscReceiver->hasWaitingMessages()){
		 ofxOscMessage msg;
		 oscReceiver->getNextMessage(&msg);
		 int t = msg.getArgAsInt32(0);
		 float l = PropF(@"latency");
		 if(latencyTimer != nil){
		 [Prop(@"latency") setFloatValue:l/2.0+-[latencyTimer timeIntervalSinceNow]/2.0];
		 latencyTimer = nil;
		 }
		 cout<<"Got pong from machine "<<t<<"  latency: "<<-[latencyTimer timeIntervalSinceNow]<<endl; 
		 }*/
		
		//Send timecode to clients
		if(-[timecodeSetterTimer timeIntervalSinceNow] > 1){
			[self sendTimecode];
		}
		
	} else { //END host
		//Clients
		
		//Client receives OSC message
		while(oscReceiver->hasWaitingMessages()){
			ofxOscMessage msg;
			oscReceiver->getNextMessage(&msg);
			if(strcmp(msg.getAddress().c_str(), "/timecode/set") == 0){
				//Receives timecode
				float t = msg.getArgAsFloat(0)+40;
				if(i != msg.getArgAsInt32(1) && PropF(@"fade") == 0)
					[Prop(@"video") setIntValue: msg.getArgAsInt32(1)];
				
				timecodeDifference = [movie[[Prop(@"video") intValue]] currentTime].timeValue - t;
				if(fabs(timecodeDifference) > 60){
					dispatch_async(dispatch_get_main_queue(), ^{						
						[movie[[Prop(@"video") intValue]] setCurrentTime:QTMakeTime(t, [movie[[Prop(@"video") intValue]] currentTime].timeScale)];
					});
				}				
			}
		}
	} // END clients
	
	
	if(ofGetFrameRate() < 20){
		NSLog(@"Low framerate: %f",ofGetFrameRate());
	}
	
	//White flash
	if(whiteFlash > 0 && ofGetFrameRate() > 20)
		whiteFlash -= 0.04*60.0/ofGetFrameRate();	
	whiteFlash = MAX(0,whiteFlash);
	
	
	if(PropF(@"fadeTo") != PropF(@"fade")){
		int dir = 1;
		if(PropF(@"fadeTo") < PropF(@"fade"))
			dir = -1;
		
		[Prop(@"fade") setFloatValue:PropF(@"fade")+dir*0.02*60.0/ofGetFrameRate()];
		
		if(dir == 1 && PropF(@"fadeTo") < PropF(@"fade")){
			//Gone to complete fade down goal
			[Prop(@"fade") setFloatValue:PropF(@"fadeTo")];
			[Prop(@"video") setFloatValue:PropF(@"fadeToVideo")];
			dispatch_async(dispatch_get_main_queue(), ^{											
				[self performSelector:@selector(setFadeTo:) withObject:[NSNumber numberWithInt:0] afterDelay:2];
			});
		}
		
		if(dir == -1 && PropF(@"fadeTo") > PropF(@"fade")){
			//Gone to complete fade up goal
			[Prop(@"fade") setFloatValue:PropF(@"fadeTo")];
		}
	}
	
	
	
	if(movie[i] != nil){
		if(lastFramesVideo != i){
			//Video change
			NSLog(@"Change video %i to %i",lastFramesVideo, i);
			
			dispatch_async(dispatch_get_main_queue(), ^{				
				//[movie[lastFramesVideo] stop];
				//[movie[i] play];	
				[movie[lastFramesVideo] setRate:0.0];
				
				if(i > 0){		
					//Går til en historie
					
					
					//Gå til slutningen af historien i base videoen
					//[movie[i] setCurrentTime:QTTimeDecrement([movie[0] currentTime], QTTimeFromString(keys[i-1][0]))];
					
					[movie[0] setCurrentTime:QTTimeFromString(keys[i-1][1])];				
					
					if(PropF(@"machine") <= 1){
						//Host
						
						whiteFlash = 1;
						forceDrawNextFrame = YES;						
					} else {
						//Clients
						//Giv besked til host at video starter
						/*ofxOscMessage m;			
						 m.setAddress("/pong");
						 m.addIntArg(PropF(@"machine"));				
						 //oscDirectSender[0]->sendMessage(m);					
						 oscDirectSender[1]->sendMessage(m);	*/
					}
					
				} else {
					//Går tilbage til base videoen
					if(PropF(@"machine") <= 1){
						//Host forces the clients to go to base video
						[self setRemoteVideos:0];
					}
					[movie[lastFramesVideo] gotoBeginning];				
				}
				
				[movie[i] setRate:1.0];	
				
				lastFramesVideo = i;
			});
		}
		
		//NSLog(@"%lld  >= %lld ",[movie[i] currentTime].timeValue , [movie[i] duration].timeValue);
		if(i > 0 && [movie[i] currentTime].timeValue >= [movie[i] duration].timeValue-40){
			//Videoen er nået til ende, så gå til video 0
			[Prop(@"video") setFloatValue:0];	
			NSLog(@"END of video");
		}		
		
		
		// check for new frame
		const CVTimeStamp * outputTime;
		[[drawingInformation objectForKey:@"outputTime"] getValue:&outputTime];
		
		QTVisualContextTask(textureContext[i]);
		
		if (textureContext[i] != NULL && QTVisualContextIsNewImageAvailable(textureContext[i], outputTime)) {
			// if we have a previous frame release it
			if (NULL != currentFrame[i]) {
				CVOpenGLTextureRelease(currentFrame[i]);
				currentFrame[i] = NULL;
			}
			// get a "frame" (image buffer) from the Visual Context, indexed by the provided time
			OSStatus status = QTVisualContextCopyImageForTime(textureContext[i], NULL, outputTime, &currentFrame[i]);
			
			// the above call may produce a null frame so check for this first
			// if we have a frame, then draw it
			if ( ( status != noErr ) && ( currentFrame[i] != NULL ) )
			{
				NSLog(@"Error: OSStatus: %d",status);
				CFRelease( currentFrame[i] );
				
				currentFrame[i] = NULL;
			} // if
			
		} else if  (textureContext[i] == NULL){
			NSLog(@"No textureContext");
			if (NULL != currentFrame[i]) {
				CVOpenGLTextureRelease(currentFrame[i]);
				currentFrame[i] = NULL;
			}
		}		
	}
}

-(void) draw:(NSDictionary*)drawingInformation{
	int i = PropF(@"video");
	
	if(currentFrame[i] != nil ){		
		//Draw video
		GLfloat topLeft[2], topRight[2], bottomRight[2], bottomLeft[2];
		
		GLenum target = CVOpenGLTextureGetTarget(currentFrame[i]);	
		GLint _name = CVOpenGLTextureGetName(currentFrame[i]);				
		
		// get the texture coordinates for the part of the image that should be displayed
		CVOpenGLTextureGetCleanTexCoords(currentFrame[i], bottomLeft, bottomRight, topRight, topLeft);
		
		
		glEnable(target);
		glBindTexture(target, _name);
		ofSetColor(255,255, 255, 255);						
		//sphereShader->setShaderActive(true);
		ApplySurface("");{
			if(0){
				glBegin(GL_QUADS);{
					glTexCoord2f(topLeft[0], topLeft[1]);  glVertex2f(0, 0);
					glTexCoord2f(topRight[0], topRight[1]);     glVertex2f(Aspect("",0),  0);
					glTexCoord2f(bottomRight[0], bottomRight[1]);    glVertex2f( Aspect("",0),  1);
					glTexCoord2f(bottomLeft[0], bottomLeft[1]); glVertex2f( 0, 1);
				}glEnd();
			} else {
				float v = 2.0;
				float gridSize = 64.0;
				
				float mesh[(int)gridSize+1][(int)gridSize+1][3];
				
				for(int x=0 ; x<=gridSize ; x++){
					for(int y=0 ; y<=gridSize ; y ++){
						mesh[x][y][0] = x/gridSize;
						mesh[x][y][1] = y/gridSize;
						mesh[x][y][2] = 0;
					}
				}
				
				for(float x=0 ; x<1 ; x+=1.0/gridSize){
					for(float y=0 ; y<1 ; y += 1.0/gridSize){
						glBegin(GL_QUADS);{
							//	NSLog(@"%f %f",x,y);
							float texX1 = topLeft[0]*(1-x) + topRight[0]*(x);
							float texY1 = topLeft[1]*(1-y) + bottomLeft[1]*(y);
							float texX2 = topLeft[0]*(1-(x+1.0/gridSize)) + topRight[0]*(x+1.0/gridSize);
							float texY2 = topLeft[1]*(1-(y+1.0/gridSize)) + bottomLeft[1]*(y+1.0/gridSize);
							
							float texCord[4][2];
							texCord[0][0] = texX1;
							texCord[0][1] = texY1;
							texCord[1][0] = texX2;
							texCord[1][1] = texY1;
							texCord[2][0] = texX2;
							texCord[2][1] = texY2;
							texCord[3][0] = texX1;
							texCord[3][1] = texY2;

							float * meshPoint[4];
							meshPoint[0]  = mesh[int(x*gridSize)][int(y*gridSize)];
							meshPoint[1]  = mesh[int(x*gridSize)+1][int(y*gridSize)];
							meshPoint[2]  = mesh[int(x*gridSize)+1][int(y*gridSize)+1];
							meshPoint[3]  = mesh[int(x*gridSize)][int(y*gridSize)+1];
							
							
							
							//cout<<""<<endl;
							for(int p=0;p<4;p++){
								glTexCoord2f(texCord[p][0], texCord[p][1]);  glVertex3f(meshPoint[p][0], meshPoint[p][1], meshPoint[p][2]);	
							}
							
							//							glTexCoord2f(texX1, texY1);  glVertex3f(x, y, meshPoint[0][0]);	
//							glTexCoord2f(texX2, texY1);  glVertex3f(x+1.0/gridSize, y, meshPoint[1][0]);	
//							glTexCoord2f(texX2, texY2);  glVertex3f(x+1.0/gridSize, y+1.0/gridSize, meshPoint[2][0]);	
//							glTexCoord2f(texX1, texY2);  glVertex3f(x, y+1.0/gridSize, meshPoint[3][0]);	
							
						}glEnd();
					}
				}
				
			}
			
		} PopSurface();
		//		sphereShader->setShaderActive(false);
		glDisable(target);
		
		QTVisualContextTask(textureContext[i]);		
	}
	
	ofEnableAlphaBlending();
	
	if(whiteFlash > 0){
		ApplySurface("");{
			ofSetColor(255,255,255, 255*whiteFlash);
			ofRect(0, 0, Aspect("",0), 1);
		} PopSurface();
	}
	
	if(PropF(@"fade") > 0){
		ApplySurface("");{
			ofSetColor(0,0,0, 255*PropF(@"fade")*1.1);
			ofRect(0, 0, Aspect("",0), 1);
		} PopSurface();
	}
	
	
	ofSetColor(255,255,255, 255);
	
	if(currentFrame[i] != nil ){				
		ofDrawBitmapString("Timecode for "+ofToString(i)+": "+[QTStringFromTime([movie[i] currentTime]) cString], 0.01, 0.02);
		ofDrawBitmapString("Difference to host: "+ofToString(timecodeDifference, 2), 0.01, 0.04);
	}	
}

@end
